package com.ezops.security;

import java.util.List;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.authc.credential.PasswordMatcher;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import com.ezops.dao.facade.DataTransferFacade;
import com.ezops.dao.generics.search.Search;
import com.ezops.models.Group;
import com.ezops.models.Resource;
import com.ezops.models.User;

@Component
public class EZOPSSecurityRealm extends AuthorizingRealm {

	final static Logger logger = LoggerFactory
			.getLogger(EZOPSSecurityRealm.class);

	@Autowired
	@Qualifier("UserDataTransferFacade")
	private DataTransferFacade<User> userFacade;

	/**
	 * TODO - User group should be set in the user object itself e.g.
	 * {@code user.getRole()} or {@code user.getUserGroups()}
	 * <p>
	 * We don't need additional facade just for fetching groups.
	 */
	@Autowired
	@Qualifier("GroupDataTransferFacade")
	private DataTransferFacade<Group> groupFacade;

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		User user = (User) principals.fromRealm(getName()).iterator().next();
		System.err.println("uerd ---" + user.getId());

		// Store the user object in Session
		SecurityUtils.getSubject().getSession().setAttribute("user", user);

		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		for (Group role : user.getUserGroups()) {
			info.addRole(role.getGroupName());
			setPermissions(info, role.getResources());
			setSuperRoles(info, role);
			// info.addStringPermissions(role.getAllowedActionName());
		}

		return info;

	}

	private void setPermissions(SimpleAuthorizationInfo info,
			List<Resource> resources) {
		info.addStringPermissions(PermissionBuilder.buildPermissions(resources));
	}

	/**
	 * Recursive method to set all super roles.
	 * 
	 * @param info
	 * @param role
	 */
	private void setSuperRoles(SimpleAuthorizationInfo info, Group role) {
		if (role.getParentGroupId() != null
				&& role.getParentGroupId().equalsIgnoreCase("NA")) {
			// Should not be String ..choose long instead
			String parentGroupId = role.getParentGroupId();
			Search searchCriteria = new Search();
			searchCriteria.addFilterEqual("groupId", parentGroupId);
			Group parentRole = this.groupFacade.searchUnique(searchCriteria);

			info.addRole(parentRole.getGroupName());
			setPermissions(info, parentRole.getResources());
			setSuperRoles(info, parentRole);
		}

	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;

		User user = null;
		try {
			// Finds the user by id
			Search searchCriteria = new Search();
			searchCriteria.addFilterEqual("userId", upToken.getUsername());

			user = this.userFacade.searchUnique(searchCriteria);

		} catch (Exception idEx) {
			throw new AuthenticationException(idEx);
		}

		if (user == null) {
			throw new AuthenticationException("Login name ["
					+ upToken.getUsername() + "] not found!");
		}
		// TODO - Force initialize to avoid lazy loading error, change the type
		// to
		// eager instead.
		for (Group group : user.getUserGroups()) {
			Hibernate.initialize(group.getResources());
		}
		logger.info("Found user with username [{}]", upToken.getUsername());
		PasswordService pwdSerice = new DefaultPasswordService();
		PasswordMatcher passwordMatcher = new PasswordMatcher();
		passwordMatcher.setPasswordService(pwdSerice);
		setCredentialsMatcher(passwordMatcher);

		return new SimpleAuthenticationInfo(user, user.getPassword(), getName());

	}
}
